package com.ljh.seckill.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.NamedThreadLocal;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @Author grung
 * @Date 2021/2/23 21:45
 * @Version 1.0
 */

@Slf4j
public class RedisLock {

    /**
     * redis操作类
     */
    private RedisTemplate redisTemplate;
    /**
     * 锁前缀 重入
     */
    private String lockName;

    /**
     * 判断删除 lua 脚本
     */
    private static DefaultRedisScript<Long> delRedisScript;
    /**
     * 判断续期 lua
     */
    private static DefaultRedisScript<Long> reentryRedisScript;
    private static final Long RELEASE_SUCCESS = 1L;
    private static final String LOCK_PRE = "LOCK:";

    /**
     * 当前线程持有的 锁标识 和 重入锁次数
     */
    LockHolder<String> lockValueHolder = new LockHolder<>();
    LockHolder<Integer> lockCountHolder = new LockHolder<>();

    static {
        delRedisScript = new DefaultRedisScript<>();
        delRedisScript.setResultType(Long.class);
        delRedisScript.setScriptText("if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end");

        reentryRedisScript = new DefaultRedisScript<>();
        reentryRedisScript.setResultType(Long.class);
        reentryRedisScript.setScriptText("if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('expire',KEYS[1],ARGV[1]) else return 0 end");
    }


    public RedisLock(RedisTemplate redisTemplate, String lockName) {
        this.redisTemplate = redisTemplate;
        this.lockName = LOCK_PRE + lockName + ":";
        log.info("RedisLock(分布式锁)初始化完成 name:{}", lockName);
    }

    /**
     *
     */
    private static final long TIME_OUT = 35000;

    /**
     * 加锁
     * <p>
     * todo 自旋检验 cpu 性能浪费很严重
     *
     * @param key     锁的name
     * @param timeout 超时时间 毫秒`
     */
    public void lock(String key, long timeout) {

        String lockKey = lockName + key;
        long start = System.currentTimeMillis();

        //可重入检验
        String oldLockVal = lockValueHolder.get(key);
        Integer count = lockCountHolder.get(key);
        if (null != oldLockVal
                && null != count
                && RELEASE_SUCCESS.equals(redisTemplate.execute(reentryRedisScript,
                Arrays.asList(lockKey, oldLockVal),
                (timeout / 1000) + ""))) {

            lockCountHolder.set(key, count + 1);
            log.info(" RedisLock(分布式锁) 重入成功,并且已经续期 key:[{}],val:{}, 过期时间:{}ms, 重入次数:{}, 耗时:{}ms",
                    lockKey, oldLockVal, timeout, count + 1, System.currentTimeMillis() - start);

            return;
        }

        while (System.currentTimeMillis() - start < timeout) {
            if (System.currentTimeMillis() - start > 10000) {
                log.error("RedisLock(分布式锁) 加锁已经超过10S key:[" + lockKey + "]");
            }

            String lockVal = UUID.randomUUID().toString().replace("-", "").toUpperCase();
            //判断是否为锁定的资源
            if (redisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, timeout, TimeUnit.MILLISECONDS)) {

                //当前线程线程保存
                lockValueHolder.set(key, lockVal);
                lockCountHolder.set(key, 1);
                log.info("RedisLock(分布式锁) 加锁成功 key:[{}], val:{}, 重入次数:1, 耗时:{}ms",
                        new Object[]{lockKey, lockVal, System.currentTimeMillis() - start});
                return;
            }
            try {
                Thread.sleep(20L);
            } catch (InterruptedException e) {
                log.error("加锁异常", e);
                throw new RuntimeException("加锁异常");
            }
        }
        throw new RuntimeException("系统繁忙请重试");
    }

    /**
     * 加锁
     *
     * @param key 锁的唯一标识
     */
    public void lock(String key) {
        lock(key, TIME_OUT);
    }

    /**
     * 解锁
     *
     * @param key 锁的key
     */
    public boolean unlock(String key) {

        String lockKey = lockName + key;
        String lockVal = lockValueHolder.get(key);
        Integer count = lockCountHolder.get(key);
        long start = System.currentTimeMillis();


        if (null == count || null == lockVal) {
            log.info("###############[RedisLock(分布式锁) 解锁异常]################:本地线程内未缓存关键信息 key:[{}], val:{}, 耗时:{}ms",
                    new Object[]{lockKey, lockVal, System.currentTimeMillis() - start});
            return false;
        }

        //未重入 直接解锁
        if (count == 1) {
            if (RELEASE_SUCCESS.equals(redisTemplate.execute(delRedisScript, Arrays.asList(lockKey, lockVal)))) {
                log.info("RedisLock(分布式锁) 解锁成功:未重入  key:[{}], val:{}, 耗时:{}ms",
                        new Object[]{lockKey, lockVal, System.currentTimeMillis() - start});
                lockValueHolder.remove(key);
                lockCountHolder.remove(key);
                return true;
            }
        } else {
            //已重入 解锁一层
            lockCountHolder.set(key, count - 1);
            log.info("RedisLock(分布式锁) 解锁成功: 已重入,解锁一层 key:[{}],val:{}, 剩余重入次数:{}, 耗时:{}ms",
                    new Object[]{lockKey, lockVal, count - 1, System.currentTimeMillis() - start});
            return true;
        }

        log.info("###############[RedisLock(分布式锁) 解锁异常]################:本地线程内未缓存关键信息key:{}, val:{}, 耗时:{}ms",
                new Object[]{lockKey, lockVal, System.currentTimeMillis() - start});
        return false;
    }

    class LockHolder<T> {
        ThreadLocal<Map<String, T>> lockHolder = new NamedThreadLocal<>("REDIS_LOCK_HOLDER_VALUE");

        public LockHolder() {
        }

        /**
         * 获取
         *
         * @param key
         * @return
         */
        public T get(String key) {
            if (null == lockHolder.get()) {
                return null;
            }
            return lockHolder.get().get(key);
        }

        /**
         * 设置
         *
         * @param key
         * @param val
         * @return
         */
        public T set(String key, T val) {
            Map<String, T> map = lockHolder.get();
            if (null == map) {
                map = initHolderMap();
            }
            return map.put(key, val);
        }

        /**
         * 获取整个map
         *
         * @return
         */
        public Map<String, T> getAll() {
            return lockHolder.get();
        }

        /**
         * 移除 指定的key
         *
         * @param key
         */
        public void remove(String key) {
            Map<String, T> map = lockHolder.get();
            if (null == map) {
                lockHolder.remove();
                return;
            }

            if (1 == map.size() && map.containsKey(key)) {
                map.remove(key);
                lockHolder.remove();
            } else {
                map.remove(key);
            }
        }

        /**
         * 初始化
         */
        private Map<String, T> initHolderMap() {
            Map<String, T> map = new HashMap<>(8);
            lockHolder.set(map);
            return map;
        }
    }
}
